package ags.muse.movement;

import static robocode.util.Utils.normalRelativeAngle;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import ags.muse.base.Rules;
import ags.muse.recon.*;
import ags.muse.base.actors.MovementActor;
import ags.util.points.AbsolutePoint;
import ags.util.points.RelativePoint;

public class CloakedEngine {
	private Rules rules;
	private RobotList robots;
	private EnemyFiring efiring;

	public CloakedEngine(Rules rules, RobotList robots, EnemyFiring efiring) {
		this.rules = rules;
		this.robots = robots;
		this.efiring = efiring;
	}

	private double testDanger(TestPoint p, double low_danger) {
		AbsolutePoint point = p.point;
		double danger = 0;

		for (EnemyRobot e : robots.getEnemies()) {
			AbsolutePoint epoint = e.getLocation();
			double dist = epoint.distance(point);
			double cdist = e.getClosestDist();
			if (cdist + 100 < dist) {
				continue;
			}

			double distratio = (dist/cdist);

			danger += 1.0/(distratio * dist);
			
			if (danger > low_danger) return danger;
		}

		danger += 0.01/Math.max(0, point.getX()-18);
		danger += 0.01/Math.max(0, point.getY()-18);
		danger += 0.01/Math.max(0, (rules.BATTLEFIELD_WIDTH-point.x)-18);
		danger += 0.01/Math.max(0, (rules.BATTLEFIELD_HEIGHT-point.y)-18);
		if (danger > low_danger) return danger;
		
		danger += getPathDanger(p, low_danger - danger);
		if (danger > low_danger) return danger;

		return danger;
	}

	private double getPathDanger(TestPoint p, double low_danger) {
		double danger = 0;
		AbsolutePoint startPoint = robots.getSelf().getLocation();
		AbsolutePoint endPoint = p.point;
		
		// Set up starting point for simulation
		RelativePoint velo = RelativePoint.fromDM(RelativePoint.fromPP(startPoint, endPoint).direction, 8);
		AbsolutePoint cursor = startPoint;
		Rectangle2D bounds = new Rectangle2D.Double(cursor.x-22, cursor.y-22, 44, 44);
		
		// Set up enemy locations
		ArrayList<Rectangle2D> enemyBounds = new ArrayList<Rectangle2D>();
		for (EnemyRobot e : robots.getEnemies()) {
			AbsolutePoint epoint = e.getLocation();
			enemyBounds.add(new Rectangle2D.Double(epoint.x-18, epoint.y-18, 36, 36));
		}
		
		// Set up simulated bullets for simulation
		ArrayList<EnemyFiring.VirtualBullet> bullets = new ArrayList<EnemyFiring.VirtualBullet>(efiring.getBullets());
		ArrayList<EnemyFiring.VirtualBullet> newBullets = new ArrayList<EnemyFiring.VirtualBullet>();
		
		boolean moving = true;
		
		for (int i=0; true; i++) {
			// If we're at destination, stop moving
			double distsq = cursor.distanceSq(endPoint);
			if (distsq < 1.0) {
				moving = false;
			}
			
			// Simulate bullets
			for (EnemyFiring.VirtualBullet b : bullets) {
				b = b.update();
				if (b == null) {
				    continue;	
				}
				else if (bounds.contains(b.location)) {
					danger += 1.0/b.duplicates;
					if (danger > low_danger) return danger;
				}
				else {
				    newBullets.add(b);
				}
			}
			bullets.clear();
			bullets.addAll(newBullets);
			newBullets.clear();
			
			// End the loop
			if (i >= 30) {
				break;
			}
			
			// Only run (privative) movement sim if still moving
			if (moving) {
				// Don't overshoot
				if (velo.magnitude*velo.magnitude > distsq) {
					velo = RelativePoint.fromDM(velo.direction, Math.sqrt(distsq));
				}

				// Increment the point
				cursor = cursor.addRelativePoint(velo);

				// Check out the danger
				bounds = new Rectangle2D.Double(cursor.x-22, cursor.y-22, 44, 44);
			}
			
			for (Rectangle2D eBound : enemyBounds) {
				if (eBound.intersects(bounds)) {
					return Double.POSITIVE_INFINITY;
				}
			}
		}

		return danger;
	}

	private class TestPoint {
		private final AbsolutePoint point;
		private final int dir;
		private TestPoint(AbsolutePoint point, int dir) {
			this.point = point;
			this.dir = dir;
		}
	}
	MovementPoly fwd_poly, rev_poly;
	TestPoint goal_point;
	public void run(MovementActor movementActor) {
		SelfRobot self = robots.getSelf();
		fwd_poly = new MovementPoly(rules, self.getLocation(), self.getVelocity(), 1, 30);
		rev_poly = new MovementPoly(rules, self.getLocation(), self.getVelocity(), -1, 30);

		// Create the test points
		List<TestPoint> test_points = new ArrayList<TestPoint>();
		test_points.add(new TestPoint(self.getLocation(), 0));
		if (goal_point != null) {
			if (goal_point.dir == 1 && !fwd_poly.contains(goal_point.point)) {
				// No longer reachable
			} else if (goal_point.dir == -1 && !rev_poly.contains(goal_point.point)) {
				// No longer reachable
			} else {
				test_points.add(goal_point);
			}
		}
		for (int i=0; i<30; i++) {
			AbsolutePoint p = fwd_poly.getRandomPoint();
			if (p != null) {
				test_points.add(new TestPoint(p, 1));
			}
		}
		for (int i=0; i<30; i++) {
			AbsolutePoint p = rev_poly.getRandomPoint();
			if (p != null) {
				test_points.add(new TestPoint(p, -1));
			}
		}

		// Test the points and set the goal point
		double low_danger = Double.POSITIVE_INFINITY;
		TestPoint best_point = goal_point;
		for (TestPoint point : test_points) {
			double danger = testDanger(point, low_danger);
			if (danger < low_danger) {
				best_point = point;
				low_danger = danger;
			}
		}
		goal_point = best_point;

		// Go to the goal point
		goToGoal(movementActor);
	}

	private void goToGoal(MovementActor movementActor) {
		RelativePoint relgoal = RelativePoint.fromPP(robots.getSelf().getLocation(), goal_point.point);

		double turn = normalRelativeAngle(relgoal.direction - robots.getSelf().getVelocity().direction);

		movementActor.setMove(relgoal.magnitude*goal_point.dir);
		movementActor.setTurnBody(turn*goal_point.dir);
	}

	public void paint(Graphics2D g) {
		g.setColor(new Color(255, 64, 64, 48));
		fwd_poly.paint(g);
		g.setColor(new Color(64, 255, 64, 48));
		rev_poly.paint(g);

		if (goal_point != null) {
			g.setColor(new Color(64, 64, 255, 128));
			g.drawOval((int)(goal_point.point.x+0.5), (int)(goal_point.point.y+0.5), 3, 3);
		}

		g.setColor(new Color(255, 64, 64, 128));
		for (EnemyRobot r : robots.getEnemies()) {
			RobotHistory h = r.getHistory();
			do {
				AbsolutePoint l = h.getLocation();
				g.drawOval((int)(l.x+0.5), (int)(l.y+0.5), 3, 3);
				h = h.prev();
			} while(h != null);
		}
	}
}
